# write by hashaki
# first edit on 2018/12/06
# last change on 2018/12/11
# 市商引擎,市商包含刷量，套利对冲，根据两个交易所目标币种的买一卖一价进行判断是刷量情况还是套利情况
# 如果要在A交易所的a品种刷量，同时与B交易所的a品种作对冲，那么先从B交易所获取a的买一卖一
# 然后根据B交易所的买一卖一与A交易所的A买一卖一作比较，当A交易所的a的买一价高于B交易所的卖一一定阈值，就可以套利，反之亦然
# 当A交易所的a买一卖一在指定区间内，就为刷量做市区间
# TODO : 从对冲交易所获取的深度要换成比例乘上目标交易所的账户余额
# TODO : 从broker模块获取账户数据的功能
# TODO : 永久运行功能
# TODO : 刷量区间内混合波动函数
# TODO : 分开线程写，获取深度行情与发单分开执行
# TODO : 套利功能
# TODO : 解决从外部传进来的挂单信息与内部下单数据的同步问题
from datetime import datetime
from math import pi,sin,cos
from collections import defaultdict

class MarketEngine:
    def __init__(self):
        '''Market类，用于做市逻辑判断，判断目前情况是刷量区间还是套利区间'''
        self.target_depth=None                         # 目标交易所a品种的全深度
        self.other_depth=None                          # 对冲交易所a品种的全深度
        self.target_exchange_symbol_ask1=None          # [价格，挂单量]
        self.target_exchange_symbol_bid1=None          # [价格，挂单量]
        self.other_exchange_symbol_ask1=None           # [价格，挂单量]
        self.other_exchange_symbol_bid1=None            # [价格，挂单量]
        self.target_exchange_time=None                 # datetime(2018,12,6,16,18,20)或者是时间戳
        self.other_exchange_time=None                  # datetime(2018,12,6,16,18,20)或者是时间戳

        self.mode=True                                 # 是刷量还是套利 True为刷量，False为套利

        self.orderList=defaultdict(list)               # 目标交易所与对冲交易所的下单列表
        self.lastOrderList=None                        # 上一个orderList
        self.tradeList=defaultdict(list)               # 目标交易所与对冲交易所成交单列表
        self.accountDict={}                            # 所有账户的信息，包括持仓和现金多少
        self.cancelList=[]                             # 需要更新的档位列表

        self.ask_precent=None                          # 用于把对冲交易所的数据加上这个百分值，伪造数据数据用的
        self.bid_precent=None                          # 用于把对冲交易所的数据加上这个百分值，伪造数据数据用的

        self.busy_time=None                            # 频繁交易时间段
        self.normal_time=None                          # 正常交易时间段

        self.slip=None                                 # 滑点
        self.size=None                                 # 手续费
        self.update_num=None                           # 档位更新触发的阈值

        self.counter_for_curve=0                       # 用于刷量功能中曲线的参数
        self.curveList=[pi/4,pi/3,pi/2,pi]             # 用于驱动正弦余弦的参数
     
        self.total_volume=None                         # total_price和total_volume为深度挂单总价格和总量
        self.pre_volume=None                           # 每笔刷量最大成交量

        self.pos=None                                  # 持仓
        self.cash=None                                 # 现金
        self.ask_volume_sum=None                       # 卖单总量
        self.bid_volume_sum=None                       # 买单总量
    
    # ----------------------------------------------------------------------------------------------------------------------------
    # 获取数据模块，包括深度行情数据，账户数据，挂单成交数据，做市设置
    def dataIn(self,target_data,other_data):
        '''从外部获取，两个交易所某品种的深度数据(10档) data={'asks':[[price,volume],...],'bids':[[price,volume],...],'time':datetime} '''
        self.target_depth=target_data
        self.other_depth=other_data
        self.target_exchange_symbol_ask1=target_data['asks'][0]      # 目标交易所买一深度数据 [price,volume]
        self.target_exchange_symbol_bid1=target_data['bids'][0]       # 目标交易所卖一深度数据
        self.other_exchange_symbol_ask1=other_data['asks'][0]        # 对冲交易所买一深度数据
        self.other_exchange_symbol_bid1=other_data['bids'][0]         # 对冲交易所卖一深度数据
        self.target_exchange_time=target_data['time']                 # 目标交易所当前深度时间
        self.other_exchange_time=other_data['time']                   # 对冲交易所当前深度时间
    
    def orderDataIn(self,order_data):
        '''从broker获取下单数据'''
        if order_data:
            self.lastOrderList=order_data
    
    def tradeDataIn(self,trade_data):
        '''从broker获取成交数据'''
        if trade_data:
            self.tradeList=trade_data
        
    def accountData(self,account_data):
        '''账户数据，包括每个账户的持仓，现金 account_data={'pos':float,'cash':float,'freeze_pos':float,'freeze':float} '''
        if account_data:
            self.accountDict=account_data
            self.pos=account_data['pos']
            self.cash=account_data['cash']
    
    def judgeSrategy(self,top_limit,bottom_limit):
        '''判断是套利区间还是刷量区间,top_limit,bottom_limit分别为上行区间边界(对冲交易所买一价加上这个limit价)，下行区间边界(对冲交易所卖一价减去这个limit价)'''
        top=self.target_exchange_symbol_bid1[0]-self.other_exchange_symbol_ask1[0]      # 目标交易所a品种的买一价与对冲交易所a品种卖一价的差值
        bottom=self.other_exchange_symbol_bid1[0]-self.target_exchange_symbol_ask1[0]   # 对冲交易所a品种的买一价与目标交易所a品种卖一价的差值

        if top / self.other_exchange_symbol_ask1[0]>top_limit:
            # 上行穿越，达到跨市套利的情况
            self.mode=False

        elif bottom / self.other_exchange_symbol_bid1[0] > bottom_limit:
            # 下行穿越，达到跨市套利的条件
            self.mode=False

        else:
            # 不是套利情况就是刷量情况了
            self.mode=True

    def precentSetting(self,precent_data):
        '''设置数据，根据对冲交易所深度数据乘上precent值伪造成目标交易所的深度数据，precent值包含ask和bid数据
        precent_data={'ask':float,'bid':float} '''
        self.ask_precent=precent_data['ask']
        self.bid_precent=precent_data['bid']

    def choseTime(self,busy_time,normal_time):
        '''选择下单时间段，白天交易次数应该比晚上多，time={'start_time':datetime,'end_time':datetime} 频繁交易时段和正常交易时段以及无交易时段'''
        if busy_time:
            self.busy_time=busy_time

        if normal_time:
            self.normal_time=normal_time

    def engineSetting(self,slip,size,update_num):
        '''手续费，滑点，跨市转账的手续费，档位更新阈值'''
        if slip:
            self.slip=slip
        if size:
            self.size=size
        if update_num:
            self.update_num=update_num
    
    def accountSetting(self,total_volume,pre_volume):
        '''账户下单数据，total_price和total_volume为深度挂单总价格和总量，per_price和per_volume为每笔刷量的价格和量'''
        if total_volume:
            self.total_volume=total_volume
        
        if pre_volume:
            self.pre_volume=pre_volume

    # ----------------------------------------------------------------------------------------------------------------
    # 中间价刷量模块
    def calculateTheMid(self):
        '''计算目标交易所买一卖一价的中间价'''
        return (self.target_exchange_symbol_ask1[0]+self.target_exchange_symbol_bid1[0])/2
        
    def sendWaveOrder(self,mode='sin'):
        '''在刷量区间内，使用一定波动率来回成交,正弦曲线的波动成交sin，余弦曲线的波动成交cos'''
        self.restartOrder()
        mid=self.calculateTheMid()
        d=self.target_exchange_symbol_ask1[0]-mid

        if self.counter_for_curve>2:
            self.counter_for_curve=0
        else:
            self.counter_for_curve+=1

        if mode=='sin':
            price=d*sin(self.curveList[self.counter_for_curve])+mid
            self.sendOrder(price,self.pre_volume)
        
        elif mode =='cos':
            pass
        
        elif mode=='mix':
            # TODO 混合曲线
            pass

        else:
            print("没有系统认识的波动曲线，若需要添加曲线，请到marketEngine.py的sendWaveOrder中添加")
            self.sendOrder(mid,self.pre_volume)

    def sendOrder(self,price,volume):
        '''买卖同时发单'''
        self.limitSell(price-3,volume)
        self.limitBuy(price+5,volume)
    
    # ------------------------------------------------------------------------------------------------------------------
    # 挂单模块
    def makeDepth(self):
        '''制造深度,根据对冲交易所的深度数据乘上一个系数来构造深度'''
        asks,bids=self.simpleDepth()
        askList=[]
        bidList=[]
        for i,k in zip(asks,bids):
            askList.append(i*(1+self.ask_precent))
            bidList.append(k*(1-self.bid_precent))
        return askList,bidList
    
    def simpleDepth(self):
        '''简单的深度构造,根据对冲交易所a品种来构造深度'''
        askList=[]
        bidList=[]
        for i in range(10):
            askList.append(self.other_depth['asks'][i][0])
            bidList.append(self.other_depth['bids'][i][0])
        return askList,bidList
    
    def simpleDepthVolume(self):
        '''简单深度挂单量构造，根据对冲交易所a品种来构造深度挂单量'''
        askList=[]
        bidList=[]
        for i in range(10):
            askList.append(self.other_depth['asks'][i][1])
            bidList.append(self.other_depth['bids'][i][1])
        return askList,bidList

    def makeDepthVolume(self):
        '''制造深度挂单量，根据对冲交易所的深度数据乘上一个系数来构造深度挂单量'''
        asks,bids=self.simpleDepthVolume()
        self.makeSumVolume(asks,bids)
        askList=[]
        bidList=[]
        for i,k in zip(asks,bids):
            temp_ask=round(i/self.ask_volume_sum*(1+self.ask_precent)*self.pos,4)
            # 限制最小单位
            if temp_ask<0.001:
                temp_ask=0.001
            askList.append(temp_ask)
            temp_bid=round(k/self.bid_volume_sum*(1-self.bid_precent)*self.pos,4)
            if temp_bid<0.001:
                temp_bid=0.001
            bidList.append(temp_bid)
        print(sum(askList)+sum(bidList))
        return askList,bidList
    
    def makeSumVolume(self,asks,bids):
        '''按照账户资金乘上挂单的比值'''
        self.ask_volume_sum=sum(asks)
        self.bid_volume_sum=sum(bids)

    def traversaDepthVolume(self):
        '''遍历深度，然后循环发单'''
        ask_price,bid_price=self.makeDepth()
        ask_volume,bid_volume=self.makeDepthVolume()

        for price_ask,volume_ask in zip(ask_price,ask_volume):
            self.limitSell(price_ask,volume_ask)
        
        for price_bid,volume_bid in zip(bid_price,bid_volume):
            self.limitBuy(price_bid,volume_bid)
        
    def updateDepth(self):
        '''判断什么时候更新不同档次的档位，前3档更新为每tick一次，中间三档为当第三档超过第四档或第三档比第四档低过某个阈值时更新，最后三档同理，1为前三档，2为中间档，3为最后三档,正为买，负为卖'''
        # 触发更新中间三档的情况,卖方情况 
        self.restart()
        if self.target_depth['asks'][2][0]>self.lastOrderList['asks'][3][0] or (self.lastOrderList['asks'][3][0]-self.target_depth['asks'][2][0])>self.update_num:
            self.cancelOrder(-2)   # 更新空方的中间档
        
        # 触发更新中间三档的情况,多方情况
        if self.target_depth['bids'][2][0]>self.lastOrderList['bids'][3][0] or (self.target_depth['bids'][2][0]-self.lastOrderList['bids'][3][0])>self.update_num:
            self.cancelOrder(2)    # 更新 多方的中间档
        
        # 触发更新后三档的情况
        if self.target_depth['asks'][5][0]>self.lastOrderList['asks'][6][0] or (self.lastOrderList['asks'][6][0]-self.target_depth['asks'][5][0])>self.update_num:
            self.cancelOrder(-3)   # 更新空方的最后档

        if self.target_depth['bids'][5][0]>self.lastOrderList['bids'][6][0] or (self.target_depth['bids'][5][0]-self.lastOrderList['bids'][6][0])>self.update_num:
            self.cancelOrder(3)    # 更新 多方的最后档
    
    def updateOrder(self):
        '''根据updateDepth更新的档位来更新要发的单'''
        # 更新买单中间档位
        if 2 in self.cancelList:
            for i in [3,3,3]:
                self.orderList['limitBuy'].pop(i)
            
            # 更新买单列表
            if 3 in self.cancelList:
                for j in [3,3,3]:
                    self.orderList['limitBuy'].pop(j)
        
        # 更新卖单中间档位
        if -2 in self.cancelList:
            for k in [3,3,3]:
                self.orderList['limitSell'].pop(k)
            
            # 更新卖单最后三档
            if -3 in self.cancelList:
                for l in [3,3,3]:
                    self.orderList['limitSell'].pop(l)
    
    def updateOnePart(self):
        '''直接更新全部更新6个档'''
        ask_price,bid_price=self.makeDepth()
        ask_volume,bid_volume=self.makeDepthVolume()

        for price_ask,volume_ask,num in zip(ask_price,ask_volume,range(6)):
            self.limitSell(price_ask,volume_ask)
        
        for price_bid,volume_bid,num in zip(bid_price,bid_volume,range(4)):
            self.limitBuy(price_bid,volume_bid)

    # ----------------------------------------------------------------------------------------------------------------
    # 套利模块
    def reLife(self):
        '''达到套利条件，但没有进行套利的情况，撤掉单重新根据对冲交易所发单'''
        if self.cancelList:
            pass
        else:
            self.cancelList=True
    
    def calculateCMAMid(self):
        '''计算目标交易所买一卖一价的中间价'''
        return (self.other_exchange_symbol_ask1[0]+self.other_exchange_symbol_bid1[0])/2
        
    def sendCMAWaveOrder(self,mode='sin'):
        '''在刷量区间内，使用一定波动率来回成交,正弦曲线的波动成交sin，余弦曲线的波动成交cos'''
        self.restartOrder()
        mid=self.calculateCMAMid()
        d=self.other_exchange_symbol_ask1[0]-mid

        if self.counter_for_curve>2:
            self.counter_for_curve=0
        else:
            self.counter_for_curve+=1

        if mode=='sin':
            price=d*sin(self.curveList[self.counter_for_curve])+mid
            self.sendOrder(price,self.pre_volume)
        
        elif mode =='cos':
            pass
        
        elif mode=='mix':
            # TODO 混合曲线
            pass

        else:
            print("没有系统认识的波动曲线，若需要添加曲线，请到marketEngine.py的sendWaveOrder中添加")
            self.sendOrder(mid,self.pre_volume)


    #-----------------------------------------------------------------------------------------------------------------
    # 两种模式都使用的功能模块
    def limitBuy(self,price,volume):
        '''限价单买进'''
        self.orderList['limitBuy'].append([round(price,4),round(volume,4)])
    
    def limitSell(self,price,volume):
        '''限价单卖出'''
        self.orderList['limitSell'].append([round(price,4),round(volume,4)])
    
    def cancelOrder(self,orderID=None):
        '''取消订单,若有orderId，则取消该id档位的单，若orderID为None则取消所有订单'''
        if orderID:
            self.cancelList.append(orderID)
        else:
            raise NotImplementedError
    
    def calculateVolume(self):
        '''计算每个深度档位量占深度总量的百分比列表'''
        ask_list,bid_list=self.simpleDepthVolume()
        ask_sum=sum(ask_list)
        bid_sum=sum(bid_list)
        ask_precent_list=[]              # 从对冲交易所的深度量按账户资产比例分配的深度量
        bid_precent_list=[]
        for i,k in zip(ask_list,bid_list):
            ask_precent_list.append(i/ask_sum)
            bid_precent_list.append(k/bid_sum)
        
        return ask_precent_list,bid_precent_list
    
    # 复位
    def restart(self):
        '''复位，有些数据需要每次运行前复位到原始'''
        self.cancelList=[]
    
    def restartOrder(self):
        '''复位order'''
        self.orderList.clear()

    def run(self):
        '''流程分叉处,判断是刷量区间还是套利区间,true为刷量分叉'''
        if self.mode:
            # 刷量区间流程分支
            # 发送刷量成交单，买一卖一中间成交
            self.sendWaveOrder()
            self.updateOnePart()
            '''
            self.traversaDepthVolume()
            if self.lastOrderList:
                # 检查需要更新的档位
                self.updateDepth()
                # 更新需要发单的档位
                self.updateOrder()
            '''

            # 把做市引擎处理的结果传出去
            return self.orderList,self.cancelList
            
        else:
            # 不是刷量就是套利啦
            print("进入到套利情况")
            self.sendCMAWaveOrder()
            self.reLife()
            self.updateOnePart()
            return self.orderList,self.cancelList